package com.morpheus.redis.lettuce.config;

import java.time.Duration;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceSentinelConnection;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import com.morpheus.redis.lettuce.annotation.ConditionalOnLettuceSentinelConfig;

import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;

@Configuration
@EnableConfigurationProperties({ RedisProperties.class, LettuceProperties.class })
@ConditionalOnClass({ RedisOperations.class, LettuceConnectionFactory.class, LettuceSentinelConnection.class })
@ConditionalOnLettuceSentinelConfig
@Order(1)
public class LettuceSentinelAutoConfiguration {
	private static final Logger LOGGER = LoggerFactory.getLogger(LettuceSentinelAutoConfiguration.class);
	private static final String DEFAULT_JMX_NAME_BASE = "lettuce";
	private static final String DEFAULT_JMX_NAME_PREFIX = "jmx";
	@Value("${spring.application.name}")
	private String appName = null;

	@SuppressWarnings("all")
	@Bean
	public GenericObjectPoolConfig poolConfig(RedisProperties redisProperties, LettuceProperties lettuceProperties) {
		LOGGER.debug("redisProperties={}, lettuceProperties={}", redisProperties, lettuceProperties);
		GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
		poolConfig.setJmxEnabled(lettuceProperties.getJmx().isEnabled());
		poolConfig.setJmxNameBase(
				Optional.ofNullable(lettuceProperties.getJmx().getName().getBase()).orElse(DEFAULT_JMX_NAME_BASE));
		poolConfig.setJmxNamePrefix(
				Optional.ofNullable(lettuceProperties.getJmx().getName().getPrefix()).orElse(DEFAULT_JMX_NAME_PREFIX));
		poolConfig.setMaxIdle(redisProperties.getLettuce().getPool().getMaxIdle());
		poolConfig.setMinIdle(redisProperties.getLettuce().getPool().getMinIdle());
		poolConfig.setMaxTotal(redisProperties.getLettuce().getPool().getMaxActive());
		poolConfig.setMaxWaitMillis(redisProperties.getLettuce().getPool().getMaxWait().toMillis());
		poolConfig.setTimeBetweenEvictionRunsMillis(
				redisProperties.getLettuce().getPool().getTimeBetweenEvictionRuns().toMillis());
		poolConfig.setTestOnBorrow(lettuceProperties.getTest().isOnBorrow());
		poolConfig.setTestOnCreate(lettuceProperties.getTest().isOnCreate());
		poolConfig.setTestOnReturn(lettuceProperties.getTest().isOnReturn());
		poolConfig.setTestWhileIdle(lettuceProperties.getTest().isWhileIdle());
		return poolConfig;
	}

	@SuppressWarnings("all")
	private LettuceClientConfiguration getLettuceClientConfiguration(GenericObjectPoolConfig poolConfig,
			LettuceProperties lettuceProperties) {
		ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
				.enableAllAdaptiveRefreshTriggers()
				.adaptiveRefreshTriggersTimeout(Duration.ofSeconds(lettuceProperties.getAdaptiveRefreshTimeout()))
				.enablePeriodicRefresh(Duration.ofSeconds(lettuceProperties.getPeriodicRefreshTimeout())).build();
		return LettucePoolingClientConfiguration.builder().poolConfig(poolConfig)
				.clientOptions(ClusterClientOptions.builder().topologyRefreshOptions(topologyRefreshOptions).build())
				.clientName(this.appName + "_lettuce").build();
	}

	@Bean
	public RedisSentinelConfiguration redisSentinelConfiguration(RedisProperties redisProperties) {
		List<String> sentinelNodes = redisProperties.getSentinel().getNodes();
		Set<String> sentinels = new HashSet<>();
		sentinels.addAll(sentinelNodes);
		RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration(
				redisProperties.getSentinel().getMaster(), sentinels);
		redisSentinelConfiguration.setPassword(RedisPassword.of(redisProperties.getPassword()));
		return redisSentinelConfiguration;
	}

	@SuppressWarnings("all")
	@Bean(destroyMethod = "destroy")
	public RedisConnectionFactory connectionFactory(RedisSentinelConfiguration sentinelConfiguration,
			GenericObjectPoolConfig poolConfig, RedisProperties redisProperties, LettuceProperties lettuceProperties) {
		LettuceClientConfiguration lettuceClientConfiguration = this.getLettuceClientConfiguration(poolConfig,
				lettuceProperties);
		LettuceConnectionFactory redisConnectionFactory = new LettuceConnectionFactory(sentinelConfiguration,
				lettuceClientConfiguration);
		redisConnectionFactory.setTimeout(redisProperties.getTimeout().toMillis());
		return redisConnectionFactory;
	}

	@Bean
	@ConditionalOnBean({ LettuceSentinelAutoConfiguration.class })
	public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) {
		StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(connectionFactory);
		return stringRedisTemplate;
	}

	@Bean(name = "redisTemplate")
	@ConditionalOnBean({ LettuceSentinelAutoConfiguration.class })
	// @ConditionalOnMissingBean(name = "redisTemplate")
	public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
		RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
		redisTemplate.setConnectionFactory(connectionFactory);
		redisTemplate.setKeySerializer(new StringRedisSerializer());
		return redisTemplate;
	}
}
